
import { Injectable } from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import * as svgCaptcha from 'svg-captcha';
import { isEmpty, omit } from 'lodash';
import * as dayjs from 'dayjs';
import { LoginDto } from './dto/login.dto';
import { SysAction, SysMenu, SysUser, UserStatusEnum } from '@/sys/entities/sys.entity';
import { ResponseJson } from '@/common/type/app.dto';
import { ResponseCode } from '@/common/type/enum';
import { UpdatePwDto } from '@/sys/dto/create-sy.dto';
import { filterTree, orderTree } from '@/common/tool/treehelper';
import { raceInit } from 'rxjs/internal/observable/race';
@Injectable()
export class LoginService {
    readonly r: ResponseJson<any, any> = { code: ResponseCode.ErrorWithMsg };
    readonly LoginMaxNum = 5;
    readonly LoginLockMinute = 10; // 登录锁定时间10分钟
    constructor(
        @InjectRepository(SysUser) private sysUser: Repository<SysUser>,
        @InjectDataSource() private datasource: DataSource
    ) {
        this.r.code = -1;
    }

    createCaptcha(): svgCaptcha.CaptchaObj {
        const c = svgCaptcha.create({
            ignoreChars: '0o1i',
        });
        return c;
    }

  

    async getAllowedActions(userId: number): Promise<Record<string ,string>> {
        const actions = await this.datasource.createQueryBuilder<SysAction>(SysAction, 'sys_action')
        .innerJoinAndSelect('sys_rolemenuaction', 'sys_rolemenuaction', 'sys_rolemenuaction.actionId = sys_action.id')        
        .innerJoinAndSelect('sys_roleuser', 'sys_roleuser', 'sys_roleuser.roleId = sys_rolemenuaction.roleId')
        .where('sys_roleuser.userId = :userId', {userId})
        .select(['sys_action.label', 'sys_action.url'])
        .distinct(true)
        .getMany();

        const sharedActions = await this.datasource.createQueryBuilder<SysAction>(SysAction, 'sys_action')
        .select(['sys_action.label label', 'sys_action.url url'])
        .where('sys_action.remark = :remark', {remark: 'shared'})
        .orWhere('sys_action.remark = :auto_remark', {auto_remark: 'auto'})
        
        .execute();
        
        
        const r = {};
        actions.forEach(element => {
            r[`${element['label']}`] = element['url']
        });
        sharedActions.forEach(element => {
            r[`${element['label']}`] = element['url']
        });
        return r;
    }


    async login(loginDto: LoginDto, session: Record<string, any>): Promise<ResponseJson<Partial<SysUser>, Record<string, string>>> {
        try {
            const user = await this.sysUser.findOne({
                where: {
                    name: loginDto.oper,
                },
                select: {
                    id: true,
                    name: true,
                    pass: true,
                    status: true,
                    cfs_id: true,
                    login_err_num: true,
                    login_err_time: true,
                },
            });

            if (isEmpty(user) || user.status === UserStatusEnum.suspend) {
                this.r.code = ResponseCode.FailWithMsg;
                this.r.msg = ['用户不存在或者已停用'];
                session['user'] = null;
                session['actions'] = [];
                return this.r;
            }
            const diff = dayjs().diff(
                user.login_err_time
                    ? user.login_err_time.toLocaleString()
                    : null,
                'minute',
            ); // 距上次错误登录分钟数
            if (
                (user.login_err_num ?? 0) >= this.LoginMaxNum &&
                diff < this.LoginLockMinute
            ) {
                this.r.code = ResponseCode.FailWithMsg;
                this.r.msg = [
                    `账号锁定，请${this.LoginLockMinute - diff}分钟后再登录`,
                ];
                session['user'] = null;
                session['actions'] = [];
                return this.r;
            }
            if (
                loginDto.captcha.toLowerCase() !==
                session['CAPTCHA'].toLowerCase()
            ) {
                this.r.code = ResponseCode.FailWithMsg;
                this.r.msg = ['验证码错误'];
                session['user'] = null;
                session['actions'] = [];
                return this.r;
            }
            if (loginDto.pw === user.pass) {
                this.r.code = ResponseCode.SuccessNoMsg;
                this.r.obj = omit(user, [
                    'pass',
                    'status',
                    'login_err_num',
                    'login_err_time',
                ]);
                this.r.ext = await this.getAllowedActions(user.id);
                session['user'] = user;
                session['actions'] = Object.values(this.r.ext);
                this.r.msg = ['登录成功'];
                user.last_time = new Date();
                user.login_err_num = 0;
                user.login_err_time = null;
                await this.sysUser.save(user);
            } else {
                this.r.code = ResponseCode.FailWithMsg;
                this.r.msg = ['密码错误'];
                session['user'] = null;
                session['actions'] = [];
                if ((user.login_err_num ?? 0) === 0) {
                    // 第一次密码错误
                    user.login_err_num = 1;
                    user.login_err_time = new Date();
                    await this.sysUser.save(user);
                } else {
                    // 非第一次错误密码，不更新错误登录时间
                    user.login_err_num = user.login_err_num + 1;
                    await this.sysUser.save(user);
                }
            }
            return this.r;
        } catch (error) {
            console.log(error);
        }
    }
    logoff(session: Record<string, any>): ResponseJson<null, null> {
        session['user'] = null;
        session['actions'] = [];
        this.r.code = ResponseCode.SuccessNoMsg;
        return this.r;
    }
    async updatePw(
        updatePw: UpdatePwDto,
        user: SysUser,
    ): Promise<ResponseJson<null, null>> {
        const r: ResponseJson<null, null> = {
            code: ResponseCode.ErrorWithMsg,
            msg: [],
        };
        if (updatePw.oper !== user.name) {
            r.msg.push('非法操作');
            // return r;
        }
        if (updatePw.oldPw !== user.pass) {
            r.msg.push('旧密码错误');
            // return
        }
        if (r.msg.length > 0) {
            return r;
        }
        const result = await this.datasource.manager.update<SysUser>(
            SysUser,
            {
                name: updatePw.oper,
            },
            {
                pass: updatePw.newPw,
            },
        );
        if (result.affected === 1) {
            user.pass = updatePw.newPw;
            r.code = ResponseCode.SuccessWithInfo;
            r.msg.push('修改成功');
        } else {
            r.msg.push('修改失败');
        }
        return r;
    }   
    async getUserMenuTree(userId: number): Promise<ResponseJson<SysMenu[], null>> {

        const menus = await this.datasource.createQueryBuilder<SysMenu>(SysMenu, 'sys_menu')
        .innerJoinAndSelect('sys_rolemenu', 'sys_rolemenu', 'sys_rolemenu.menuId = sys_menu.id')        
        .innerJoinAndSelect('sys_roleuser', 'sys_roleuser', 'sys_roleuser.roleId = sys_rolemenu.roleId')        
        .select(['sys_menu.id'])
        .where('sys_roleuser.userId = :userId', {userId})
        .andWhere("sys_menu.label != '通用'")
        .distinct(true)
        .getMany();

        const tree = await this.datasource.manager
        .getTreeRepository(SysMenu)
        .findTrees();   
        const menuIds = [];
        menus.forEach(element => {
            menuIds.push(element.id);
        });

        const r = filterTree(tree, menuIds);
        const newtree = orderTree(r) as SysMenu[];
        return {
            code: ResponseCode.SuccessNoMsg,
            obj: newtree
        };
    }     
}
